﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProgNetComponents.Log;
using System.Data;
using ProgNetComponents.Data;

namespace ProgNetDxComponents.ExtenderProviders.Browsers
{
    public sealed class BrowserCache
    {
        public static event EventHandler<GetBrowserDataEventArgs> GetStaticData;

        private static BrowserCollection _AllBrowsers = new BrowserCollection();
        private static Dictionary<string, List<string>> BrowsersByTable = new Dictionary<string, List<string>>();
        private static Dictionary<string, List<string>> AccessibleFrom = new Dictionary<string, List<string>>();
        private static Dictionary<string, DataTable> BrowsersData = new Dictionary<string, DataTable>();
        private static BrowserDataGrabberBase _DataGrabber;

        public static BrowserDataGrabberBase DataGrabber
        {
            get { return _DataGrabber; }
            set
            {
                if (_DataGrabber != null)
                    _DataGrabber.LastUpdateTimeChanged -= new EventHandler<LastUpdateTimeChangedEventArgs>(value_LastUpdateTimeChanged);
                _DataGrabber = value;
                if (_DataGrabber != null)
                    _DataGrabber.LastUpdateTimeChanged += new EventHandler<LastUpdateTimeChangedEventArgs>(value_LastUpdateTimeChanged);
            }
        }

        private static void value_LastUpdateTimeChanged(object sender, LastUpdateTimeChangedEventArgs e)
        {
            List<string> browsers = null;
            if (BrowsersByTable.ContainsKey(e.Table))
            {
                browsers = BrowsersByTable[e.Table];
                Browser br = null;
                foreach (string b in browsers)
                {
                    br = GetBrowser(b, false);
                    if (br.RefreshType == BrowserRefreshDataType.OnRefresh)
                    {
                        GetBrowserData(b);
                    }
                }
            }
        }


        public static BrowserConfigReader ConfigReader
        {
            get;
            set;
        }

        private static Logger _Logger = null;
        /// <summary>
        /// Gets or sets Logger. If not set Logger.Default will be used
        /// </summary>
        public static Logger Logger
        {
            get
            {
                if (_Logger == null)
                    return Logger.Default;
                return _Logger;
            }
            set { _Logger = value; }
        }

        /// <summary>
        /// Gets Browser or it's clone from cache
        /// </summary>
        /// <param name="name"></param>
        /// <param name="clone"></param>
        /// <returns></returns>
        public static Browser GetBrowser(string name, bool clone = true)
        {
            Browser b = null;
            b = _AllBrowsers[name];
            if (b != null && clone)
            {
                b = b.Clone() as Browser;
            }
            if (b == null && "".IsAppRunning())
                Logger.Log("BrowserCache.GetBrowser() - No browser " + name.AddDoubleQuotes());
            return b;
        }

        public static bool BrowserHasCachedData(string browserName)
        {
            return BrowsersData.ContainsKeySafe(browserName);
        }
        /// <summary>
        /// Adds browser to cache
        /// </summary>
        /// <param name="b"></param>
        public static void RegisterBrowser(Browser b)
        {
            if (b != null)
            {
                ConfigReader.AddConfig(b.ConfigGroup);
                if (!_AllBrowsers.Contains(b.Name))
                {
                    _AllBrowsers.Add(b);
                    if (!b.Static)
                    {
                        if (b.Tables != null && b.Tables.Length > 0)
                        {
                            foreach (string table in b.Tables)
                            {
                                List<string> l = null;
                                if (BrowsersByTable.ContainsKey(table))
                                {
                                    l = BrowsersByTable[table];
                                }
                                else
                                {
                                    l = new List<string>();
                                    BrowsersByTable.Add(table, l);
                                }
                                l.Add(b.Name);
                            }
                            CheckAccessibleFrom(b);
                        }
                        else
                            Logger.Log("RegisterBrowser -> No tables for browser " + b.Name.AddDoubleQuotes());
                    }
                }
                else
                {
                    Logger.Log("RegisterBrowser -> Duplicated browser name " + b.Name.AddDoubleQuotes());
                }
            }
        }
        public static void Register(BrowserCollection browserCollection)
        {
            foreach (Browser b in browserCollection)
                RegisterBrowser(b);
        }
        private static void CheckAccessibleFrom(Browser browser)
        {
            if (!browser.Static && browser.Name.IsNotNull() && browser.Tables.IsNotNull())
            {
                Browser other = null;
                bool match = false;
                string table = browser.Tables[0];
                List<string> browsers = null;
                if (AccessibleFrom.ContainsKey(browser.Name))
                {
                    browsers = AccessibleFrom[browser.Name];
                }
                if (BrowsersByTable.ContainsKey(table))
                {

                    foreach (string s in BrowsersByTable[table])
                    {
                        if (s != browser.Name)
                        {
                            if (!browsers.Contains(s))
                            {
                                other = GetBrowser(s, false);
                                if (other != null && !other.Static
                                    && other.Tables.Length == browser.Tables.Length &&
                                    other.SelectFilter.IsNull())
                                {
                                    match = true;
                                    foreach (string t in other.Tables)
                                    {
                                        if (!browser.Tables.Contains(t))
                                        {
                                            match = false;
                                            break;
                                        }
                                    }
                                    if (match)
                                    {
                                        foreach (BrowserColumn col in browser.Columns)
                                        {
                                            if (!other.Columns.Contains(col.Name))
                                            {
                                                match = false;
                                                break;
                                            }
                                        }
                                    }
                                    if (match)
                                    {

                                        if (browsers == null)
                                        {
                                            browsers = new List<string>();
                                            AccessibleFrom.Add(browser.Name, browsers);
                                        }
                                        if (!browsers.Contains(other.Name))
                                        {
                                            browsers.Add(other.Name);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Gets browsers data from cache, database or static data (using GetStaticData event)
        /// </summary>
        /// <param name="browser"></param>
        /// <param name="onlyCache"></param>
        /// <returns></returns>
        public static DataTable GetBrowserData(string browser, bool onlyCache = false)
        {
            Browser br = GetBrowser(browser, false);
            if (br == null)
                return null;
            if (br.Static)
            {
                DataTable dt = null;
                if (BrowsersData.ContainsKey(browser))
                {
                    dt = BrowsersData[browser];
                    if (br.GetDataOnce)
                    {
                        return dt;
                    }
                }
                if (GetStaticData != null)
                {
                    GetBrowserDataEventArgs e = new GetBrowserDataEventArgs(br.Name);
                    GetStaticData(br, e);
                    if (e.Table != null && br.GetDataOnce)
                    {
                        if (!BrowsersData.ContainsKey(browser))
                        {
                            BrowsersData.Add(browser, e.Table);
                        }
                    }
                    return e.Table;
                }
            }
            else
            {
                if (br != null)
                {
                    if (BrowsersData.ContainsKey(browser))
                    {
                        if (DataGrabber.IsBrowserUpToDate(br))
                            return BrowsersData[browser];
                    }
                    if (AccessibleFrom.ContainsKey(browser))
                    {
                        DataTable dt =null;
                        Browser other = null;
                        List<DataColumn> delColumns = new List<DataColumn>();
                        foreach (string s in AccessibleFrom[browser])
                        {
                            other = GetBrowser(s, false);
                            if (other != null && DataGrabber.IsBrowserUpToDate(other))
                            {
                                dt = GetBrowserData(s, true);
                                if (dt != null && other != null)
                                {
                                    dt = dt.Copy(br.SelectFilter);
                                    foreach (DataColumn col in dt.Columns)
                                    {
                                        if (!br.Columns.Contains(col.ColumnName))
                                        {
                                            delColumns.Add(col);
                                        }
                                    }
                                    foreach (DataColumn c in delColumns)
                                        dt.Columns.Remove(c);
                                    if (BrowsersData.ContainsKey(browser))
                                    {
                                        BrowsersData[browser].Clear();
                                        foreach (DataRow r in dt.Rows)
                                        {
                                            BrowsersData[browser].ImportRow(r);
                                        }
                                        return BrowsersData[browser];
                                    }
                                    else
                                    {
                                        BrowsersData.Add(browser, dt);
                                        return dt;
                                    }
                                }
                            }
                        }
                    }
                    if (!onlyCache)
                    {
                        DataTable dt = null;
                        if (BrowsersData.ContainsKey(browser))
                        {
                            dt = BrowsersData[browser];
                        }
                        else
                        {
                            dt = new DataTable(browser);
                            BrowsersData.Add(browser, dt);
                        }
                        DataGrabber.FillBrowserDataFromDB(br.Name, dt);
                        DataGrabber.RefreshLastUpdateDate(br);
                        return dt;
                    }
                }
            }
            return null;
        }
        /// <summary>
        /// Gets DataRow for specified value
        /// </summary>
        /// <param name="browser"></param>
        /// <param name="value"></param>
        /// <param name="additionalFilter"></param>
        /// <param name="cacheOnly"></param>
        /// <returns></returns>
        public static DataRow GetRowForValue(string browser, object value, string additionalFilter = null, bool cacheOnly = true)
        {
            DataRow r = null;
            DataTable dt = GetBrowserData(browser, true);
            Browser b = GetBrowser(browser, false);
            if (b != null)
            {
                string filter = additionalFilter;
                if (filter.IsNotNull())
                    filter = " ( " + filter + ") AND ";
                filter += b.ValueMember + " " + (value.IsNull() ? " IS NULL" : (" = " + value.AsString().EscapeSql()));
                if (dt != null)
                    r = dt.SelectFirstRow(filter);
                else if (!cacheOnly)
                {
                    dt = new DataTable(browser);
                    DataGrabber.FillBrowserDataFromDB(b.Name, dt, filter);
                    if (dt.Rows.Count == 1)
                        r = dt.Rows[0];
                }
            }
            return r;
        }
        public static DataRow GetRowForDisplayValue(string browser, object value, string additionalFilter = null, bool cacheOnly = true)
        {
            DataRow r = null;
            DataTable dt = GetBrowserData(browser, true);
            Browser b = GetBrowser(browser, false);
            if (b != null)
            {
                string filter = additionalFilter;
                if (filter.IsNotNull())
                    filter = " ( " + filter + ") AND ";
                filter += b.DisplayMember + " " + (value.IsNull() ? " IS NULL" : (" = " + value.AsString().EscapeSql()));
                if (dt != null)
                    r = dt.SelectFirstRow(filter);
                else if (!cacheOnly)
                {
                    dt = new DataTable(browser);
                    DataGrabber.FillBrowserDataFromDB(b.Name, dt, filter);
                    if (dt.Rows.Count == 1)
                        r = dt.Rows[0];
                }
            }
            return r;
        }
        public static string GetDisplayForValue(string browser, object value)
        {
            DataRow r = GetRowForValue(browser, value);
            if (r != null)
            {
                Browser br = GetBrowser(browser, false);
                if (br != null)
                {
                    return r[br.DisplayMember].AsString();
                }
            }
            return string.Empty;
        }
        
        public static BrowserDisplayType GetBrowserDisplayTypeFormConfig(string browser,string additionalFilter = null)
        {
            return ConfigReader.GetDisplayType(browser, additionalFilter);
        }

        public static bool IsValueValid(string browser, object value, string additionalFilter = null)
        {
            if (value.IsNull())
                return true;

            Browser br = GetBrowser(browser, false);
            bool onlyCache = GetBrowserDisplayTypeFormConfig(browser, additionalFilter) != BrowserDisplayType.Dialog && GetBrowserData(browser, true) != null;
            return GetRowForValue(browser, value, additionalFilter, onlyCache) != null;
        }
        public static bool IsDisplayValueValid(string browser, object value, string additionalFilter = null)
        {
            if (value.IsNull())
                return true;

            Browser br = GetBrowser(browser, false);
            bool onlyCache = GetBrowserDisplayTypeFormConfig(browser, additionalFilter) != BrowserDisplayType.Dialog && GetBrowserData(browser, true) != null;
            return GetRowForDisplayValue(browser, value, additionalFilter, onlyCache) != null;
        }
        public static int GetBrowserRecordCount(string browser, string additionalFilter = null)
        {
            DataTable dt = GetBrowserData(browser, true);
            if (dt != null)
                return dt.Select(additionalFilter).Length;
            Browser br = GetBrowser(browser, false);
            if (!br.Static)
                return DataGrabber.GetRecordCountFromDB(browser, additionalFilter);
            Logger.Log("BrowserCache.GetBrowserRecordCount() : Problem with getting record count for browser " + browser);
            return -1;
        }

        public ObjectDialogResult ShowBrowserDialog(string browser, bool allowNew = false, string additionalFilter = null, object editvalue = null)
        {
            Browser b = GetBrowser(browser, false);
            if (b != null)
                return b.ShowDialog(editvalue, allowNew, additionalFilter);
            return ObjectDialogResult.DefaultResult;
        }

        public static object GetFirstId(string browserName)
        {
            if (browserName.IsNotNull())
            {
                browserName = browserName.ToUpper();
                DataTable d = GetBrowserData(browserName);

                if (d.Rows.Count > 0 && _AllBrowsers.Contains(browserName))
                {
                    return d.Rows[0][_AllBrowsers[browserName].ValueMember];
                }
            }
            return null;
        }
        public static object GetLastId(string browserName)
        {
            if (browserName.IsNotNull())
            {
                browserName = browserName.ToUpper();
                DataTable d = GetBrowserData(browserName);

                if (d.Rows.Count > 0 && _AllBrowsers.Contains(browserName))
                {
                    return d.Rows[d.Rows.Count - 1][_AllBrowsers[browserName].ValueMember];
                }
            }
            return null;
        }
        private static object GetId(string browserName, object currentId, bool next)
        {
            if (browserName.IsNotNull() && currentId.IsNotNull())
            {
                browserName = browserName.ToUpper();
                DataTable d = GetBrowserData(browserName);
                if (_AllBrowsers.Contains(browserName))
                {
                    string col = _AllBrowsers[browserName].ValueMember;
                    DataRow r = d.SelectFirstRow(col + " = " + currentId.AsString().EscapeSql());
                    if (r != null)
                    {
                        int idx = d.Rows.IndexOf(r);
                        if (next)
                            idx++;
                        else
                            idx--;
                        if (idx >= 0 && idx < d.Rows.Count)
                            return d.Rows[idx][col];
                    }
                }
            }
            return null;
        }
        
        public static object GetNextId(string browserName, object currentId)
        {
            return GetId(browserName, currentId, true);
        }
        public static object GetPrevId(string browserName, object currentId)
        {
            return GetId(browserName, currentId, false);
        }


        
    }

    public class GetBrowserDataEventArgs : EventArgs
    {
        public GetBrowserDataEventArgs(String browser)
        {
            _Browser = browser;
        }
        private string _Browser;

        public string Browser
        {
            get { return _Browser; }
        }
        public DataTable Table
        {
            get;
            set;
        }
    }
}
